﻿#include "DAProject.h"
// Qt
#include <QBuffer>
#include <QDomDocument>
#include <QFile>
#include <QScopedPointer>
#include <QVariant>
#include <QPen>
#include <QElapsedTimer>
#include <QSet>
// DA
#include "DAWorkFlowGraphicsScene.h"
#include "DAAppPluginManager.h"
#include "DAGraphicsResizeablePixmapItem.h"
#include "DAStandardNodeLinkGraphicsItem.h"
#include "DAGraphicsItemFactory.h"
#include "DAAbstractNodeFactory.h"
#include "DAGraphicsItem.h"
#include "DAXMLFileInterface.h"
#include "DAStringUtil.h"
#include "DAWorkFlowOperateWidget.h"
#include "DAWorkFlowEditWidget.h"
namespace DA
{
class DAProjectPrivate
{
public:
    DA_IMPL_PUBLIC(DAProject)
    DAProjectPrivate(DAProject* p);

public:
    //保存工作流
    void saveWorkflow(DAWorkFlowEditWidget* wfe, QDomDocument& doc, QDomElement& workflowEle);
    bool loadWorkflow(DAWorkFlowEditWidget* wfe, const QDomElement& workflowEle);
    //保存工厂相关的扩展信息
    void saveFactoryInfo(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle);
    bool loadFactoryInfo(DAWorkFlow* workflow, const QDomElement& workflowEle);
    //保存工作流的节点
    void saveNodes(const DAWorkFlowEditWidget* wfe, QDomDocument& doc, QDomElement& workflowEle);
    bool loadNodes(DAWorkFlowEditWidget* wfe, const QDomElement& workflowEle);
    //保存输入输出
    void saveNodeInputOutput(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle);
    bool loadNodeInPutOutputKey(DAAbstractNode::SharedPointer& node, const QDomElement& eleNode);
    //保存属性
    void saveNodePropertys(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle);
    bool loadNodePropertys(DAAbstractNode::SharedPointer& node, const QDomElement& nodeEle);
    //保存item
    void saveNodeItem(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle);
    bool loadNodeItem(DAWorkFlowGraphicsScene* scene, DAAbstractNode::SharedPointer& node, const QDomElement& nodeEle);
    //保存链接
    void saveNodeLinks(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle);
    bool loadNodeLinks(DAWorkFlowGraphicsScene* scene, DAWorkFlow* wf, const QDomElement& workflowEle);
    //保存特殊的item，主要为文本
    void saveSpecialItem(const DAWorkFlowGraphicsScene* scene, QDomDocument& doc, QDomElement& workflowEle);
    bool loadSpecialItem(DAWorkFlowGraphicsScene* scene, const QDomElement& workflowEle);

    // SecenInfo
    void saveSecenInfo(DAWorkFlowGraphicsScene* scene, QDomDocument& doc, QDomElement& workflowEle);
    bool loadSecenInfo(DAWorkFlowGraphicsScene* scene, const QDomElement& workflowEle);
    //

    //生成一个qvariant element
    QDomElement createVariantValueElement(QDomDocument& doc, const QVariant& var);
    QVariant loadVariantValueElement(const QDomElement& item, const QVariant& defaultVal) const;
    //带提示的属性转double
    qreal attributeToDouble(const QDomElement& item, const QString& att);

protected:
    DAWorkFlowOperateWidget* _workFlowOperateWidget;
    QSet< QGraphicsItem* > _haveBeenSaveNodeItem;  ///< 记录已经被保存过的node item
};
}  // end of namespace DA

//===================================================
// using DA namespace -- 禁止在头文件using！！
//===================================================

using namespace DA;

//===================================================
// DAProjectPrivate
//===================================================
DAProjectPrivate::DAProjectPrivate(DAProject* p) : q_ptr(p), _workFlowOperateWidget(nullptr)
{
}
/**
 * @brief 保存工作流相关信息
 * @param workflow
 * @param doc
 * @param workflowEle
 */
void DAProjectPrivate::saveWorkflow(DAWorkFlowEditWidget* wfe, QDomDocument& doc, QDomElement& workflowEle)
{
    //保存工作流的扩展信息
    QElapsedTimer tes;
    tes.start();
    _haveBeenSaveNodeItem.clear();  //清空保存过的item的记录
    QDomElement externEle                  = doc.createElement("extern");
    DAWorkFlow* workflow                   = wfe->getWorkflow();
    DAWorkFlowGraphicsScene* workFlowScene = wfe->getWorkFlowGraphicsScene();
    workflow->saveExternInfoToXml(&doc, &externEle);
    workflowEle.appendChild(externEle);
    qDebug() << DAProject::tr("save workflow extern info cost: %1 ms").arg(tes.restart());
    //保存所有节点
    saveNodes(wfe, doc, workflowEle);
    qDebug() << DAProject::tr("save workflow nodes cost: %1 ms").arg(tes.restart());
    //保存所有连接
    saveNodeLinks(workflow, doc, workflowEle);
    qDebug() << DAProject::tr("save workflow links cost: %1 ms").arg(tes.restart());
    //保存特殊的item。例如文本
    saveSpecialItem(workFlowScene, doc, workflowEle);
    qDebug() << DAProject::tr("save special item cost: %1 ms").arg(tes.restart());
    //保存工厂相关信息，包括工厂的扩展信息，工厂的信息一般在node和link之后保存
    saveFactoryInfo(workflow, doc, workflowEle);
    qDebug() << DAProject::tr("save workflow factory info cost: %1 ms").arg(tes.restart());
    //保存scene信息
    saveSecenInfo(workFlowScene, doc, workflowEle);
    qDebug() << DAProject::tr("save secen info cost: %1 ms").arg(tes.restart());
}

/**
 * @brief 加载工作流相关信息
 * @param workflow
 * @param workflowEle
 * @return
 */
bool DAProjectPrivate::loadWorkflow(DAWorkFlowEditWidget* wfe, const QDomElement& workflowEle)
{
    //加载扩展信息
    QElapsedTimer tes;
    tes.start();
    DAWorkFlow* workflow                   = wfe->getWorkflow();
    DAWorkFlowGraphicsScene* workFlowScene = wfe->getWorkFlowGraphicsScene();
    //

    QDomElement externEle = workflowEle.firstChildElement("extern");
    workflow->loadExternInfoFromXml(&externEle);
    qDebug() << DAProject::tr("load workflow extern info cost: %1 ms").arg(tes.restart());

    if (!loadNodes(wfe, workflowEle)) {
        qCritical() << DAProject::tr("load nodes occurce error");
    }
    qDebug() << DAProject::tr("load workflow nodes cost: %1 ms").arg(tes.restart());

    if (!loadNodeLinks(workFlowScene, workflow, workflowEle)) {
        qCritical() << DAProject::tr("load nodes link occurce error");
    }
    qDebug() << DAProject::tr("load workflow links cost: %1 ms").arg(tes.restart());

    if (!loadSpecialItem(workFlowScene, workflowEle)) {
        qCritical() << DAProject::tr("load special item occurce error");
    }
    qDebug() << DAProject::tr("load special item cost: %1 ms").arg(tes.restart());
    //加载工厂
    //! 注意，工厂的加载要在节点和连接之后，工厂一般维护着全局的数据，因此
    //! 额外信息一般是在节点和连接都完成后进行加载
    if (!loadFactoryInfo(workflow, workflowEle)) {
        qCritical() << DAProject::tr("load factorys occurce error");
    }
    qDebug() << DAProject::tr("load workflow factory info cost: %1 ms").arg(tes.restart());

    if (!loadSecenInfo(workFlowScene, workflowEle)) {
        qCritical() << DAProject::tr("load scene info occurce error");
    }
    qDebug() << DAProject::tr("load secen info cost: %1 ms").arg(tes.restart());
    return true;
}

/**
 * @brief 保存工厂相关的扩展信息
 * @param workflow
 * @param doc
 * @param workflowEle
 */
void DAProjectPrivate::saveFactoryInfo(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle)
{
    QList< DAAbstractNodeFactory* > factorys = workflow->factorys();
    //创建节点
    QDomElement factorysEle = doc.createElement("factorys");
    for (const DAAbstractNodeFactory* fac : qAsConst(factorys)) {
        QDomElement factoryEle = doc.createElement("factory");
        factoryEle.setAttribute("prototypes", fac->factoryPrototypes());
        QDomElement externEle = doc.createElement("extern");
        fac->saveExternInfoToXml(&doc, &externEle);  //保存扩展信息
        factoryEle.appendChild(externEle);
        factorysEle.appendChild(factoryEle);
    }
    workflowEle.appendChild(factorysEle);
}

/**
 * @brief 加载工厂相关的扩展信息
 * @param workflow
 * @param workflowEle
 * @return
 */
bool DAProjectPrivate::loadFactoryInfo(DAWorkFlow* workflow, const QDomElement& workflowEle)
{
    QDomElement factorysEle     = workflowEle.firstChildElement("factorys");
    QDomNodeList factoryEleList = factorysEle.childNodes();

    for (int i = 0; i < factoryEleList.size(); ++i) {
        QDomElement factoryEle = factoryEleList.at(i).toElement();
        if (factoryEle.tagName() != "factory") {
            qWarning() << DAProject::tr("find unknow tag <%1> under <factorys> element").arg(factoryEle.tagName());
            continue;
        }
        QString factoryPrototypes  = factoryEle.attribute("prototypes");
        DAAbstractNodeFactory* fac = workflow->getFactory(factoryPrototypes);
        if (fac == nullptr) {
            qCritical() << DAProject::tr("can not find factory prototypes = %1").arg(factoryPrototypes);
            continue;
        }
        QDomElement externEle = factoryEle.firstChildElement("extern");
        fac->loadExternInfoFromXml(&externEle);  //加载工厂的信息
    }
    return true;
}

void DAProjectPrivate::saveNodes(const DAWorkFlowEditWidget* wfe, QDomDocument& doc, QDomElement& workflowEle)
{
    DAWorkFlow* workflow                                = wfe->getWorkflow();
    const QList< DAAbstractNode::SharedPointer >& nodes = workflow->nodes();

    QDomElement nodesEle = doc.createElement("nodes");
    for (const DAAbstractNode::SharedPointer& node : qAsConst(nodes)) {
        QDomElement nodeEle = doc.createElement("node");
        nodeEle.setAttribute("id", node->getID());
        nodeEle.setAttribute("name", node->getNodeName());
        nodeEle.setAttribute("protoType", node->getNodePrototype());

        //保存节点input和output的key和propertys
        saveNodeInputOutput(node, doc, nodeEle);
        //保存节点属性
        saveNodePropertys(node, doc, nodeEle);
        //保存额外信息
        node->saveExternInfoToXml(&doc, &nodeEle);
        //添加节点Item信息
        saveNodeItem(node, doc, nodeEle);
        nodesEle.appendChild(nodeEle);
    }
    workflowEle.appendChild(nodesEle);
}

bool DAProjectPrivate::loadNodes(DAWorkFlowEditWidget* wfe, const QDomElement& workflowEle)
{
    DAWorkFlow* workflow                   = wfe->getWorkflow();
    DAWorkFlowGraphicsScene* workFlowScene = wfe->getWorkFlowGraphicsScene();
    QDomElement nodesEle                   = workflowEle.firstChildElement("nodes");
    QDomNodeList nodeslist                 = nodesEle.childNodes();

    for (int i = 0; i < nodeslist.size(); ++i) {
        QDomElement nodeEle = nodeslist.at(i).toElement();
        if (nodeEle.tagName() != "node") {
            qDebug() << "nodeEle.tagName()=" << nodeEle.tagName() << ",skip and continue";
            continue;
        }
        bool isok     = false;
        qulonglong id = nodeEle.attribute("id").toULongLong(&isok);
        if (!isok) {
            qWarning() << DAProject::tr("node's id=%1 can not conver to qulonglong type ,will skip this node")
                          .arg(nodeEle.attribute("id"));
            continue;
        }
        QString name      = nodeEle.attribute("name");
        QString protoType = nodeEle.attribute("protoType");
        //创建节点
        DANodeMetaData metadata            = workflow->getNodeMetaData(protoType);
        DAAbstractNode::SharedPointer node = workflow->createNode(metadata);
        if (nullptr == node) {
            qWarning() << DAProject::tr("workflow can not create note by "
                                        "metadata(prototype=%1,name=%2,group=%3),will skip this node")
                          .arg(metadata.getNodePrototype(), metadata.getNodeName(), metadata.getGroup());
            continue;
        }

        node->setID(id);
        node->setNodeName(name);
        //加载节点的输入输出
        loadNodeInPutOutputKey(node, nodeEle);
        //加载节点的属性
        loadNodePropertys(node, nodeEle);
        //加载额外信息
        node->loadExternInfoFromXml(&nodeEle);
        //加载item
        loadNodeItem(workFlowScene, node, nodeEle);
        //最后再添加
        workflow->addNode(node);
    }
    return true;
}

/**
 * @brief 生成一个qvariant element
 * @param doc
 * @param v
 * @return
 */
QDomElement DAProjectPrivate::createVariantValueElement(QDomDocument& doc, const QVariant& var)
{
    return DAXMLFileInterface::makeElement(var, "variant", &doc);
}

QVariant DAProjectPrivate::loadVariantValueElement(const QDomElement& item, const QVariant& defaultVal) const
{
    QVariant res;
    if (DAXMLFileInterface::loadElement(res, &item)) {
        return res;
    }
    return defaultVal;
}
/**
 * @brief 带警告的attribute转double
 * @param item
 * @param att
 * @return
 */
qreal DAProjectPrivate::attributeToDouble(const QDomElement& item, const QString& att)
{
    bool isok = false;
    qreal r   = item.attribute(att).toDouble(&isok);
    if (!isok) {
        qWarning() << DAProject::tr("The attribute %1=%2 under the tag %3 cannot be converted to double ")
                      .arg(att, item.attribute(att), item.tagName());
    }
    return r;
}

/**
 * @brief DAProjectPrivate::saveNodeInputOutput
 * @param node
 * @param doc
 * @param nodeEle
 */
void DAProjectPrivate::saveNodeInputOutput(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle)
{
    QDomElement inputsEle      = doc.createElement("inputs");
    QList< QString > inputKeys = node->getInputKeys();
    for (const auto& key : qAsConst(inputKeys)) {
        //添加input
        QDomElement inputEle = doc.createElement("input");
        QDomElement nameEle  = doc.createElement("name");
        QDomText nameText    = doc.createTextNode(key);
        nameEle.appendChild(nameText);
        inputEle.appendChild(nameEle);
        QVariant v = node->getInputData(key);
        if (v.isValid()) {
            QDomElement varEle = createVariantValueElement(doc, v);
            inputEle.appendChild(varEle);
        }
        inputsEle.appendChild(inputEle);
    }

    QDomElement outputsEle      = doc.createElement("outputs");
    QList< QString > outputKeys = node->getOutputKeys();
    for (const auto& key : qAsConst(outputKeys)) {
        //添加output
        QDomElement outputEle = doc.createElement("output");
        QDomElement nameEle   = doc.createElement("name");
        QDomText nameText     = doc.createTextNode(key);
        nameEle.appendChild(nameText);
        outputEle.appendChild(nameEle);
        QVariant v = node->getOutputData(key);
        if (v.isValid()) {
            QDomElement varEle = createVariantValueElement(doc, v);
            outputEle.appendChild(varEle);
        }
        outputsEle.appendChild(outputEle);
    }
    nodeEle.appendChild(inputsEle);
    nodeEle.appendChild(outputsEle);
}

bool DAProjectPrivate::loadNodeInPutOutputKey(DAAbstractNode::SharedPointer& node, const QDomElement& eleNode)
{
    QDomElement e = eleNode.firstChildElement("inputs");
    if (!e.isNull()) {
        QDomNodeList ks = e.childNodes();
        for (int i = 0; i < ks.size(); ++i) {
            QDomElement inputEle = ks.at(i).toElement();
            if (!inputEle.isNull() && inputEle.tagName() != "input") {
                continue;
            }
            QDomElement nameEle = inputEle.firstChildElement("name");
            if (nameEle.isNull()) {
                qWarning() << DAProject::tr("node(prototype=%1,name=%2,group=%3) %4 tag loss child tag <name>")
                              .arg(node->getNodePrototype(), node->getNodeName(), node->getNodeGroup(), ks.at(i).nodeName());
                continue;
            }
            QString key = nameEle.text();
            node->addInputKey(key);
            QDomElement variantEle = inputEle.firstChildElement("variant");
            if (!variantEle.isNull()) {
                QVariant d = loadVariantValueElement(variantEle, QVariant());
                node->setInputData(key, d);
            }
        }
    }

    e = eleNode.firstChildElement("outputs");
    if (!e.isNull()) {
        QDomNodeList ks = e.childNodes();
        for (int i = 0; i < ks.size(); ++i) {
            QDomElement outputEle = ks.at(i).toElement();
            if (!outputEle.isNull() && outputEle.tagName() != "output") {
                continue;
            }
            QDomElement nameEle = outputEle.firstChildElement("name");
            if (nameEle.isNull()) {
                qWarning() << DAProject::tr("node(prototype=%1,name=%2,group=%3) %4 tag loss child tag <name>")
                              .arg(node->getNodePrototype(), node->getNodeName(), node->getNodeGroup(), ks.at(i).nodeName());
                continue;
            }
            QString key = nameEle.text();
            node->addOutputKey(key);
            QDomElement variantEle = outputEle.firstChildElement("variant");
            if (!variantEle.isNull()) {
                QVariant d = loadVariantValueElement(variantEle, QVariant());
                node->setOutputData(key, d);
            }
        }
    }
    return true;
}
/**
 * @brief 保存节点的附加属性
 * @param node
 * @param doc
 * @param nodeEle
 */
void DAProjectPrivate::saveNodePropertys(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle)
{
    QDomElement propertysEle      = doc.createElement("propertys");
    QList< QString > propertyKeys = node->getPropertyKeys();
    for (const QString& k : qAsConst(propertyKeys)) {
        QVariant v = node->getProperty(k);
        if (!v.isValid()) {
            continue;
        }
        QDomElement nameEle = doc.createElement("name");
        QDomText nameVal    = doc.createTextNode(k);
        nameEle.appendChild(nameVal);
        QDomElement varEle      = createVariantValueElement(doc, v);
        QDomElement propertyEle = doc.createElement("property");
        propertyEle.appendChild(nameEle);
        propertyEle.appendChild(varEle);
        propertysEle.appendChild(propertyEle);
    }
    nodeEle.appendChild(propertysEle);
}

bool DAProjectPrivate::loadNodePropertys(DAAbstractNode::SharedPointer& node, const QDomElement& nodeEle)
{
    QDomElement propertysEle = nodeEle.firstChildElement("propertys");
    if (propertysEle.isNull()) {
        return false;
    }
    QDomNodeList propertyslist = propertysEle.childNodes();
    for (int i = 0; i < propertyslist.size(); ++i) {
        QDomElement propertyEle = propertyslist.at(i).toElement();
        if (propertyEle.tagName() != "property") {
            continue;
        }
        QDomElement nameEle = propertyEle.firstChildElement("name");
        if (nameEle.isNull()) {
            continue;
        }
        QDomElement variantEle = propertyEle.firstChildElement("variant");
        if (variantEle.isNull()) {
            continue;
        }
        QVariant v = loadVariantValueElement(variantEle, QVariant());
        node->setProperty(nameEle.text(), v);
    }
    return true;
}

void DAProjectPrivate::saveNodeItem(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle)
{
    DAAbstractNodeGraphicsItem* item = node->graphicsItem();
    QDomElement itemEle              = doc.createElement("item");
    item->saveToXml(&doc, &itemEle);
    nodeEle.appendChild(itemEle);
    _haveBeenSaveNodeItem.insert(item);
}

bool DAProjectPrivate::loadNodeItem(DAWorkFlowGraphicsScene* scene, DAAbstractNode::SharedPointer& node, const QDomElement& nodeEle)
{
    QDomElement ele = nodeEle.firstChildElement("item");
    if (ele.isNull()) {
        return false;
    }
    DAAbstractNodeGraphicsItem* item = node->createGraphicsItem();
    if (nullptr == item) {
        qWarning() << DAProject::tr("node metadata(prototype=%1,name=%2,group=%3) can not create graphics item")
                      .arg(node->getNodePrototype(), node->getNodeName(), node->getNodeGroup());
        return false;
    }
    item->loadFromXml(&ele);
    scene->addItem(item);
    return true;
}

void DAProjectPrivate::saveNodeLinks(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle)
{
    const QList< DAAbstractNode::SharedPointer >& nodes = workflow->nodes();
    //
    QDomElement nodeLinkEle = doc.createElement("links");

    QSet< DAAbstractNodeLinkGraphicsItem* > itemSet;
    for (auto node : nodes) {
        auto links = node->graphicsItem()->getLinkItems();
        for (auto link : qAsConst(links)) {
            itemSet.insert(link);
        }
    }

    for (auto link : itemSet) {
        QDomElement linkEle = doc.createElement("link");
        QDomElement fromEle = doc.createElement("from");
        fromEle.setAttribute("id", link->fromNode()->getID());
        fromEle.setAttribute("name", link->fromNodeLinkPoint().name);
        QDomElement toEle = doc.createElement("to");
        toEle.setAttribute("id", link->toNode()->getID());
        toEle.setAttribute("name", link->toNodeLinkPoint().name);

        QDomElement pointEle = doc.createElement("linkPoint");
        pointEle.setAttribute("visible", link->isLinkPointNameVisible());
        pointEle.setAttribute("fromTextColor",
                              link->getLinkPointNameTextColor(DAAbstractNodeLinkGraphicsItem::OrientationFrom).name());
        pointEle.setAttribute("toTextColor",
                              link->getLinkPointNameTextColor(DAAbstractNodeLinkGraphicsItem::OrientationTo).name());
        pointEle.setAttribute("fromPositionOffset",
                              link->getLinkPointNamePositionOffset(DAAbstractNodeLinkGraphicsItem::OrientationFrom));
        pointEle.setAttribute("toPositionOffset", link->getLinkPointNamePositionOffset(DAAbstractNodeLinkGraphicsItem::OrientationTo));
        QDomElement endPointEle = doc.createElement("endPoint");
        endPointEle.setAttribute("toType", DA::enumToString(link->getEndPointType(DAAbstractNodeLinkGraphicsItem::OrientationTo)));
        endPointEle.setAttribute("fromType",
                                 DA::enumToString(link->getEndPointType(DAAbstractNodeLinkGraphicsItem::OrientationFrom)));
        endPointEle.setAttribute("size", link->getEndPointSize());

        QDomElement lineEle = doc.createElement("linkLine");
        lineEle.setAttribute("style", DA::enumToString(link->getLinkLineStyle()));

        QDomElement linePenEle = doc.createElement("linePen");
        linePenEle.setAttribute("color", link->getLinePen().color().name());
        linePenEle.setAttribute("style", (link->getLinePen().style()));
        linePenEle.setAttribute("width", link->getLinePen().width());

        linkEle.appendChild(fromEle);
        linkEle.appendChild(toEle);
        linkEle.appendChild(pointEle);
        linkEle.appendChild(endPointEle);
        linkEle.appendChild(lineEle);
        linkEle.appendChild(linePenEle);

        nodeLinkEle.appendChild(linkEle);
    }

    workflowEle.appendChild(nodeLinkEle);
}

bool DAProjectPrivate::loadNodeLinks(DAWorkFlowGraphicsScene* scene, DAWorkFlow* wf, const QDomElement& workflowEle)
{
    QDomElement linksEle = workflowEle.firstChildElement("links");
    QDomNodeList list    = linksEle.childNodes();
    for (int i = 0; i < list.size(); ++i) {
        QDomElement linkEle = list.at(i).toElement();
        if (linkEle.tagName() != "link") {
            continue;
        }
        QDomElement fromEle = linkEle.firstChildElement("from");
        QDomElement toEle   = linkEle.firstChildElement("to");
        if (fromEle.isNull() || toEle.isNull()) {
            continue;
        }
        bool ok = false;

        qulonglong id                          = fromEle.attribute("id").toULongLong(&ok);
        QString fromKey                        = fromEle.attribute("name");
        DAAbstractNode::SharedPointer fromNode = wf->getNode(id);
        if (!ok || nullptr == fromNode) {
            qWarning() << DAProject::tr("link info can not find node in workflow,id = %1").arg(fromEle.attribute("id"));
            continue;
        }
        id                                   = toEle.attribute("id").toULongLong(&ok);
        QString toKey                        = toEle.attribute("name");
        DAAbstractNode::SharedPointer toNode = wf->getNode(id);
        if (!ok || nullptr == toNode) {
            qWarning() << DAProject::tr("link info can not find node in workflow,id = %1").arg(toEle.attribute("id"));
            continue;
        }
        DAAbstractNodeGraphicsItem* fromItem = fromNode->graphicsItem();
        DAAbstractNodeGraphicsItem* toItem   = toNode->graphicsItem();
        if (nullptr == fromItem || nullptr == toItem) {
            qWarning() << DAProject::tr("can not get item by node");
            continue;
        }
        QScopedPointer< DAStandardNodeLinkGraphicsItem > linkitem(new DAStandardNodeLinkGraphicsItem());
        if (!linkitem->attachFrom(fromItem, fromKey)) {
            qWarning() << DAProject::tr("link item can not attach from node item(%1) with key=%2").arg(fromItem->getNodeName(), fromKey);
            continue;
        }
        if (!linkitem->attachTo(toItem, toKey)) {
            qWarning() << DAProject::tr("link item can not attach to node item(%1) with key=%2").arg(toItem->getNodeName(), toKey);
            continue;
        }
        QDomElement pointEle    = linkEle.firstChildElement("linkPoint");
        QDomElement endPointEle = linkEle.firstChildElement("endPoint");
        QDomElement lineEle     = linkEle.firstChildElement("linkLine");
        QDomElement linePenEle  = linkEle.firstChildElement("linePen");
        if (!pointEle.isNull()) {
            bool visible = pointEle.attribute("visible").toInt();
            QColor fromTextColor(pointEle.attribute("fromTextColor"));
            QColor toTextColor(pointEle.attribute("toTextColor"));
            int fromPositionOffset = pointEle.attribute("fromPositionOffset").toInt();
            int toPositionOffset   = pointEle.attribute("toPositionOffset").toInt();
            linkitem->setLinkPointNameVisible(visible);
            linkitem->setLinkPointNamePositionOffset(fromPositionOffset, DAAbstractNodeLinkGraphicsItem::OrientationFrom);
            linkitem->setLinkPointNamePositionOffset(toPositionOffset, DAAbstractNodeLinkGraphicsItem::OrientationTo);
            linkitem->setLinkPointNameTextColor(fromTextColor, DAAbstractNodeLinkGraphicsItem::OrientationFrom);
            linkitem->setLinkPointNameTextColor(toTextColor, DAAbstractNodeLinkGraphicsItem::OrientationTo);
        }
        if (!lineEle.isNull()) {
            DAAbstractNodeLinkGraphicsItem::LinkLineStyle s = DA::stringToEnum(lineEle.attribute("style"),
                                                                               DAAbstractNodeLinkGraphicsItem::LinkLineKnuckle);
            linkitem->setLinkLineStyle(s);
        }
        if (!endPointEle.isNull()) {
            DAAbstractNodeLinkGraphicsItem::EndPointType etTo   = DA::stringToEnum(endPointEle.attribute("toType"),
                                                                                 DAAbstractNodeLinkGraphicsItem::EndPointNone);
            DAAbstractNodeLinkGraphicsItem::EndPointType etFrom = DA::stringToEnum(endPointEle.attribute("fromType"),
                                                                                   DAAbstractNodeLinkGraphicsItem::EndPointNone);
            int size                                            = endPointEle.attribute("size").toInt();
            linkitem->setEndPointType(DAAbstractNodeLinkGraphicsItem::OrientationTo, etTo);
            linkitem->setEndPointType(DAAbstractNodeLinkGraphicsItem::OrientationFrom, etFrom);
            if (size > 0) {
                linkitem->setEndPointSize(size);
            }
        }
        if (!linePenEle.isNull()) {
            QColor pencolor(linePenEle.attribute("color"));
            int width          = linePenEle.attribute("width").toInt();
            Qt::PenStyle style = static_cast< Qt::PenStyle >(linePenEle.attribute("style").toInt());
            QPen pen(pencolor);
            pen.setWidth(width);
            pen.setStyle(style);
            linkitem->setLinePen(pen);
        }
        DAStandardNodeLinkGraphicsItem* rawlinkitem = linkitem.take();
        scene->addItem(rawlinkitem);
        rawlinkitem->updatePos();
    }
    return true;
}

/**
 * @brief 保存特殊的item
 * @param scene
 * @param doc
 * @param workflowEle
 */
void DAProjectPrivate::saveSpecialItem(const DAWorkFlowGraphicsScene* scene, QDomDocument& doc, QDomElement& workflowEle)
{
    //保存文本
    QList< QGraphicsItem* > items = scene->items();
    QDomElement itemsElement      = doc.createElement("items");
    for (const QGraphicsItem* i : qAsConst(items)) {
        if (_haveBeenSaveNodeItem.contains(const_cast< QGraphicsItem* >(i))) {
            //已经保存过的不进行保存
            continue;
        }
        const DAXMLFileInterface* xml = dynamic_cast< const DAXMLFileInterface* >(i);
        if (nullptr == xml) {
            //非xml文件系统
            continue;
        }
        switch (i->type()) {
        case DA::ItemType_GraphicsStandardTextItem: {
            //文本
            QDomElement itemElement = doc.createElement("item");
            itemElement.setAttribute("className", "DA::DAStandardGraphicsTextItem");
            const DAStandardGraphicsTextItem* ti = static_cast< const DAStandardGraphicsTextItem* >(i);
            ti->saveToXml(&doc, &itemElement);
            itemsElement.appendChild(itemElement);
        } break;
        default: {
            const DAGraphicsItem* obj = dynamic_cast< const DAGraphicsItem* >(i);
            if (nullptr == obj) {
                qWarning() << QObject::tr("There is a item that is not a DA Graphics Item system and cannot be saved");  //存在不是DA Graphics Item系统的元件，无法对此元件进行保存
                continue;
            }
            QDomElement itemElement = doc.createElement("item");
            itemElement.setAttribute("className", obj->metaObject()->className());
            xml->saveToXml(&doc, &itemElement);
            itemsElement.appendChild(itemElement);
        }
        }
    }
    workflowEle.appendChild(itemsElement);
}

/**
 * @brief 加载特殊的item
 * @param scene
 * @param workflowEle
 * @return
 */
bool DAProjectPrivate::loadSpecialItem(DAWorkFlowGraphicsScene* scene, const QDomElement& workflowEle)
{
    QDomElement specialItemsElement = workflowEle.firstChildElement("items");
    DAGraphicsItemFactory itemFactory;
    QDomNodeList childNodes = specialItemsElement.childNodes();
    for (int i = 0; i < childNodes.size(); ++i) {
        QDomElement itemEle = childNodes.at(i).toElement();
        if (itemEle.tagName() == "item") {
            QString className = itemEle.attribute("className");
            if (className.isEmpty()) {
                continue;
            }
            if (className == "DA::DAStandardGraphicsTextItem") {
                DAStandardGraphicsTextItem* item = new DAStandardGraphicsTextItem();
                if (!item->loadFromXml(&itemEle)) {
                    qWarning() << QObject::tr("Unable to load item information from file").arg(className);  //无法通过文件加载元件信息
                } else {
                    scene->addItem(item);
                }
            } else {
                DAGraphicsItem* item = itemFactory.createItem(className);
                if (nullptr == item) {
                    qWarning() << QObject::tr("Cannot create item by class name:%1").arg(className);  //无法通过类名:%1创建元件
                    continue;
                } else {
                    if (!item->loadFromXml(&itemEle)) {
                        qWarning() << QObject::tr("Unable to load item information from file").arg(className);  //无法通过文件加载元件信息
                        itemFactory.destoryItem(item);
                    } else {
                        scene->addItem(item);
                    }
                }
            }
        }
    }
    return true;
}

void DAProjectPrivate::saveSecenInfo(DAWorkFlowGraphicsScene* scene, QDomDocument& doc, QDomElement& workflowEle)
{
    QDomElement sceneEle = doc.createElement("scene");
    QRectF sceneRect     = scene->sceneRect();
    sceneEle.setAttribute("x", sceneRect.x());
    sceneEle.setAttribute("y", sceneRect.y());
    sceneEle.setAttribute("width", sceneRect.width());
    sceneEle.setAttribute("height", sceneRect.height());
    auto item = scene->getBackgroundPixmapItem();
    if (nullptr != item) {
        QPixmap pixmap = item->getPixmap();
        if (!pixmap.isNull()) {
            QBuffer buff;
            pixmap.save(&buff, "PNG");
            QByteArray dataimg;
            //图像转换为数据
            dataimg.append(buff.data());
            //图片保存在字符串中
            QString imgStr = dataimg.toBase64();
            //以Base64保存到工程文件
            QDomElement imageEle = doc.createElement("background");
            QDomText imageText   = doc.createTextNode(imgStr);
            imageEle.appendChild(imageText);
            sceneEle.appendChild(imageEle);
        }
    }

    workflowEle.appendChild(sceneEle);
}

bool DAProjectPrivate::loadSecenInfo(DAWorkFlowGraphicsScene* scene, const QDomElement& workflowEle)
{
    QDomElement sceneEle = workflowEle.firstChildElement("scene");
    if (sceneEle.isNull()) {
        return false;
    }
    // 暂时不对scene的尺寸做限制
    //    qreal x      = attributeToDouble(sceneEle, "x");
    //    qreal y      = attributeToDouble(sceneEle, "y");
    //    qreal width  = attributeToDouble(sceneEle, "width");
    //    qreal height = attributeToDouble(sceneEle, "height");
    //    scene->setSceneRect(x, y, width, height);

    QDomElement imageEle = sceneEle.firstChildElement("background");
    if (!imageEle.isNull()) {
        QString imgStr = imageEle.text();

        //字符串转图像数据
        QByteArray imgData = QByteArray::fromBase64(imgStr.toUtf8());
        QPixmap pixmap;
        //从数据载入图像
        pixmap.loadFromData(imgData);
        if (!pixmap.isNull()) {
            scene->setBackgroundPixmap(pixmap);
        }
    }
    return true;
}

////////////////////////////////////////////////////

DAProject::DAProject(DACoreInterface* c, QObject* p) : DAProjectInterface(c, p), d_ptr(new DAProjectPrivate(this))
{
}

DAProject::~DAProject()
{
}
/**
 * @brief 设置工作流操作窗口
 * @param w
 */
void DAProject::setWorkFlowOperateWidget(DAWorkFlowOperateWidget* w)
{
    d_ptr->_workFlowOperateWidget = w;
}

/**
 * @brief 保存工程
 *
 * @note 工作流保存过程如下：
 * -# 保存工作流扩展信息
 * -# 保存节点信息
 * -# 保存链接信息
 * -# 保存特殊item（非工作流的item）
 * -# 保存工厂扩展信息
 * -# 保存scene信息
 * @param path
 * @return
 */
bool DAProject::save(const QString& path)
{
    Q_CHECK_PTR(d_ptr->_workFlowOperateWidget);
    QFile file(path);
    if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
        qCritical() << tr("open failed,path is %1").arg(path);
        return false;
    }
    QDomDocument doc;
    QDomProcessingInstruction processInstruction = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
    doc.appendChild(processInstruction);
    QDomElement root = doc.createElement("root");
    root.setAttribute("type", "project");
    doc.appendChild(root);
    QDomElement project = doc.createElement("project");
    root.appendChild(project);
    //把所有的工作流保存
    QDomElement workflowsElement = doc.createElement("workflows");
    project.appendChild(workflowsElement);
    int wfcount = d_ptr->_workFlowOperateWidget->count();
    for (int i = 0; i < wfcount; ++i) {
        QDomElement workflowEle   = doc.createElement("workflow");
        DAWorkFlowEditWidget* wfe = d_ptr->_workFlowOperateWidget->getWorkFlowWidget(i);
        QString name              = d_ptr->_workFlowOperateWidget->getWorkFlowWidgetName(i);
        workflowEle.setAttribute("name", name);
        d_ptr->saveWorkflow(wfe, doc, workflowEle);
        workflowsElement.appendChild(workflowEle);
    }
    int currentIndex = d_ptr->_workFlowOperateWidget->getCurrentWorkflowIndex();
    workflowsElement.setAttribute("currentIndex", currentIndex);
    QTextStream outFile(&file);
    doc.save(outFile, 4);
    file.close();
    setProjectPath(path);
    emit projectSaved(path);
    setDirty(false);
    return true;
}
/**
 * @brief 加载
 *
 * @note 工作流加载过程如下：
 * -# 加载工作流扩展信息
 * -# 加载节点信息
 * -# 加载链接信息
 * -# 加载特殊item（非工作流的item）
 * -# 加载工厂扩展信息
 * -# 加载scene信息
 * @param path
 * @return
 */
bool DAProject::load(const QString& path)
{
    //加载之前先清空
    Q_CHECK_PTR(d_ptr->_workFlowOperateWidget);
    clear();
    bool isok = appendProjectWorkflow(path, true);
    if (isok) {
        setProjectPath(path);
        emit projectLoaded(path);
    }
    return isok;
}

/**
 * @brief 清除工程
 */
void DAProject::clear()
{
    Q_CHECK_PTR(d_ptr->_workFlowOperateWidget);
    d_ptr->_workFlowOperateWidget->clear();
    DAProjectInterface::clear();
}

/**
 * @brief 把一个工程追加到当前工程中
 * @param path
 * @param skipIndex 是否跳转到保存的tab索引
 */
bool DAProject::appendProjectWorkflow(const QString& path, bool skipIndex)
{
    //加载之前先清空
    Q_CHECK_PTR(d_ptr->_workFlowOperateWidget);
    QDomDocument doc;
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly)) {
        return false;
    }
    QString error;
    if (!doc.setContent(&file, &error)) {
        qCritical() << "load setContent error:" << error;
        file.close();
        return false;
    }
    file.close();
    int oldProjectHaveWorkflow = d_ptr->_workFlowOperateWidget->count();  //已有的工作流数量
    bool isok                  = true;
    QDomElement docElem        = doc.documentElement();                  // root
    QDomElement proEle         = docElem.firstChildElement("project");   // project
    QDomElement workflowsEle   = proEle.firstChildElement("workflows");  // workflows
    QDomNodeList wfListNodes   = workflowsEle.childNodes();
    QSet< QString > names      = d_ptr->_workFlowOperateWidget->getAllWorkflowNames().toSet();
    for (int i = 0; i < wfListNodes.size(); ++i) {
        QDomElement workflowEle = wfListNodes.at(i).toElement();
        if (workflowEle.tagName() != "workflow") {
            continue;
        }
        QString name = workflowEle.attribute("name");
        //生成一个唯一名字
        name = DA::makeUniqueString(names, name);
        //建立工作流窗口
        DAWorkFlowEditWidget* wfe = d_ptr->_workFlowOperateWidget->appendWorkflow(name);
        isok &= d_ptr->loadWorkflow(wfe, workflowEle);
    }
    if (skipIndex) {
        int index = workflowsEle.attribute("currentIndex").toInt();
        index += oldProjectHaveWorkflow;
        d_ptr->_workFlowOperateWidget->setCurrentWorkflow(index);
    }
    return isok;
}

/**
 * @brief 工程文件的后缀
 * @return
 */
QString DAProject::getProjectFileSuffix()
{
    return "asproj";
}
